/** * Most of the code in the Qalingo project is copyrighted Hoteia and licensed * under the Apache License Version 2.0 (release version 0.8.0) * http://www.apache.org/licenses/LICENSE-2.0 * <p/> * Copyright (c) Hoteia, 2012-2014 * http://www.hoteia.com - http://twitter.com/hoteia - contact@hoteia.com */ package org.hoteia.qalingo.core.aop.cache; import java.lang.reflect.Method; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.ehcache.Cache; import org.hoteia.qalingo.core.annotation.CacheEntityInformation; import org.hoteia.qalingo.core.annotation.CacheMethodInformation; import org.hoteia.qalingo.core.annotation.CacheType; import org.hoteia.qalingo.core.domain.AbstractEntity; import org.hoteia.qalingo.core.domain.impl.DomainEntity; import org.hoteia.qalingo.core.fetchplan.FetchPlan; import org.hoteia.qalingo.core.pojo.AbstractPojo; import org.hoteia.qalingo.core.web.cache.util.CacheService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component(value = "cacheManagementAspect") public class CacheManagementAspect { private final Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private CacheService cacheService; public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object returnObject = null; try { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class classReturnType = signature.getReturnType(); Object[] args = joinPoint.getArgs(); String cacheType = null; String cacheName = null; Cache cache = null; String key = null; logger.debug("Start Cache AOP. Call from : '" + joinPoint.getSignature().toShortString() + "'"); // DEFINE THE CACHE TYPE try { CacheMethodInformation cacheMethodInformation = signature.getMethod().getAnnotation(CacheMethodInformation.class); cacheType = cacheMethodInformation.cacheType(); logger.debug("CacheMethodInformation from annotation : cacheType= '" + cacheType + "'"); cacheName = cacheMethodInformation.cacheName(); logger.debug("CacheEntityInformation from annotation : cacheName= '" + cacheName + "'"); } catch (Exception e) { // TODO: handle exception } if(DomainEntity.class.isAssignableFrom(classReturnType)){ cacheType = CacheType.CACHE_ENTITY; if (joinPoint.getSignature().toShortString().contains("ByCode")) { // FIRST ARG IS A STRING FOR THE GET METHOD : SO THIS A GET BY CODE cacheType = CacheType.CACHE_LINK_CODE_ID; } } else if(AbstractPojo.class.isAssignableFrom(classReturnType)){ cacheType = CacheType.CACHE_POJO; } logger.debug("Cache Type : '" + cacheType + "'"); // TARGETED FETCH PLAN FetchPlan askedFetchPlan = null; FetchPlan loadedFetchPlan = null; for (Object arg : args) { if (arg instanceof Object[]) { Object[] objects = (Object[]) arg; for (Object object : objects) { if (object instanceof FetchPlan) { FetchPlan fetchPlan = (FetchPlan) object; if (fetchPlan != null && !fetchPlan.getFetchModes().isEmpty()) { askedFetchPlan = fetchPlan; break; } } } } } if(StringUtils.isNotEmpty(cacheType)){ try { CacheEntityInformation cacheEntityInformation = (CacheEntityInformation) classReturnType.getAnnotation(CacheEntityInformation.class); cacheName = cacheEntityInformation.cacheName(); // CHECK THE NAME OF THE CACHE if (cacheType.equals(CacheType.CACHE_LINK_CODE_ID)) { cacheName += "_link_code_id"; } logger.debug("CacheEntityInformation from annotation : cacheName= '" + cacheName + "'"); } catch (Exception e) { // TODO: handle exception } if(StringUtils.isNotEmpty(cacheName)){ // LOAD THE CACHE if (cacheType.equals(CacheType.CACHE_ENTITY)) { String id = null; if(args != null && args.length > 0){ if(args[0] instanceof Long){ id = ((Long) args[0]).toString(); } } key = cacheService.buildEntityKey(signature.getReturnType(), id); if(id == null){ // OVERRIDE THE KEY BY THE METHOD key = signature.toShortString(); } cache = cacheService.getCache(cacheName, String.class, AbstractEntity.class); } else if (cacheType.equals(CacheType.CACHE_LINK_CODE_ID)) { String code = null; if(args != null && args.length > 0){ if(args[0] instanceof String){ code = ((String) args[0]).toString(); } } key = cacheService.buildCodeIdKey(signature, code); if(code == null){ // OVERRIDE THE KEY BY THE METHOD key = signature.toShortString(); } cache = cacheService.getCache(cacheName, String.class, Long.class); } else if (cacheType.equals(CacheType.CACHE_POJO)) { key = cacheService.buildCommonKey(signature, args); cache = cacheService.getCache(cacheName, String.class, AbstractPojo.class); } else if (cacheType.equals(CacheType.CACHE_STRING)) { key = cacheService.buildCommonKey(signature, args); cache = cacheService.getCache(cacheName, String.class, String.class); } // TEST IF THE VALUE IS IN CACHE if (cache != null) { logger.debug("Searching object in cache with : key= '" + key + "'"); if (cache.containsKey(key)) { logger.debug("Object exist in cache with : key= '" + key + "'"); Object element = cache.get(key); if (element != null) { // WE TEST IF THE FETCH PLAN ARE EQUALS returnObject = element; if (cacheType.equals(CacheType.CACHE_ENTITY)) { returnObject = checkFetchPlan(returnObject, askedFetchPlan, loadedFetchPlan); } else if (cacheType.equals(CacheType.CACHE_LINK_CODE_ID)) { String cacheNameEntity = cacheName.replace("_link_code_id", ""); Long id = (Long) returnObject; returnObject = null; Cache cacheEntity = cacheService.getCache(cacheNameEntity, String.class, AbstractEntity.class); String keyEntity = cacheService.buildEntityKey(signature.getReturnType(), id.toString()); if (cacheEntity.containsKey(keyEntity)) { Object entity = cacheEntity.get(keyEntity); returnObject = entity; returnObject = checkFetchPlan(returnObject, askedFetchPlan, loadedFetchPlan); } } } } else { logger.debug("Object doesn't exist in cache with : key= '" + key + "'"); } } } } // NOTHING IN CACHE - CALL THE TARGET METHOD if (returnObject == null) { if (loadedFetchPlan != null) { args = ArrayUtils.add(args, loadedFetchPlan); returnObject = joinPoint.proceed(args); } else { returnObject = joinPoint.proceed(); } if (returnObject != null && cache != null) { // PUT IN CACHE if (cacheType.equals(CacheType.CACHE_ENTITY)) { logger.debug("Put in cache '" + cacheName + "'. key : '" + key + "'. value: '" + returnObject + "'"); cache.put(key, returnObject); // PUT THE CODE/ID String cacheNameCodeId = cacheName + "_link_code_id"; Cache cacheCodeId = cacheService.getCache(cacheNameCodeId, String.class, Long.class); Long id = (Long) handleClassMethodGetValue(joinPoint, returnObject, classReturnType, "getId"); String code = (String) handleClassMethodGetValue(joinPoint, returnObject, classReturnType, "getCode"); String newKey = key = cacheService.buildCodeIdKey(signature, code); logger.debug("Put in cache '" + cacheNameCodeId + "'. key : '" + newKey + "'. value: '" + id + "'"); cacheCodeId.put(newKey, id); } else if (cacheType.equals(CacheType.CACHE_LINK_CODE_ID)) { Long id = (Long) handleClassMethodGetValue(joinPoint, returnObject, classReturnType, "getId"); logger.debug("Put in cache '" + cacheName + "'. key : '" + key + "'. value: '" + id + "'"); cache.put(key, id); // PUT THE ENTITY String cacheNameEntity = cacheName.replace("_link_code_id", ""); Cache cacheEntity = cacheService.getCache(cacheNameEntity, String.class, AbstractEntity.class); String newKey = cacheService.buildEntityKey(signature.getReturnType(), id.toString()); logger.debug("Put in cache '" + cacheName + "'. key : '" + newKey + "'. value: '" + returnObject + "'"); cacheEntity.put(newKey, returnObject); } else if (cacheType.equals(CacheType.CACHE_POJO)) { logger.debug("Put in cache '" + cacheName + "'. key : '" + key + "'. value: '" + returnObject + "'"); cache.put(key, returnObject); } else if (cacheType.equals(CacheType.CACHE_STRING)) { logger.debug("Put in cache '" + cacheName + "'. key : '" + key + "'. value: '" + returnObject + "'"); cache.put(key, returnObject); } } } } catch (Exception e) { logger.error("Failed to load datas with Cache AOP! Call from : '" + joinPoint.getSignature().toShortString() + "'", e); } logger.debug("End Cache AOP. Call from : '" + joinPoint.getSignature().toShortString() + "'"); logger.debug("---------------------------------------------------------------------------------"); return returnObject; } protected Object handleClassMethodGetValue(ProceedingJoinPoint joinPoint, Object returnObject, Class classReturnType, String methode) { try { Method[] methods = classReturnType.getMethods(); for (Method methodIt : methods) { if (methodIt.getName().equals(methode)) { return methodIt.invoke(returnObject); } } } catch (Exception e) { logger.debug("Exception code. Call from : '" + joinPoint.getSignature().toShortString() + "'", e); } return null; } protected Object checkFetchPlan(Object returnObject, FetchPlan askedFetchPlan, FetchPlan loadedFetchPlan){ if (returnObject instanceof DomainEntity) { AbstractEntity entity = (AbstractEntity) returnObject; if (entity.getFetchPlan() != null) { loadedFetchPlan = entity.getFetchPlan(); } } if (askedFetchPlan != null) { if (loadedFetchPlan != null && !loadedFetchPlan.containAllTargetFetchPlans(askedFetchPlan)) { // ENTITY IS LOAD WITHOUT FETCHPLAN - WE RESET THE returnObject TO TRIGGER THE RELOAD WITH THE FETCHPLAN // WE WILL ADD LOADED FETCH PLAN AND ASKED FETCH PLAN TO THE INVOCATED METHOD returnObject = null; } } return returnObject; } }